#include "gstreamer_8.h"

#define CHUNK_SIZE 1024
#define SAMPLE_RATE 44100

typedef struct _CustomData
{
    GstElement *pipeline;

    GstElement *app_src;
    GstElement *tee;

    GstElement *audio_queue;
    GstElement *audio_convert;
    GstElement *audio_resample;
    GstElement *audio_sink;

    GstElement *video_queue;
    GstElement *audio_convert1;
    GstElement *visual;
    GstElement *video_convert;
    GstElement *video_sink;

    GstElement *app_queue;
    GstElement *app_sink;

    guint num_samples;
    gfloat a, b, c, d;

    guint sourceid;

    GMainLoop *main_loop;

} CustomData;

static void start_feed(GstElement *, guint, CustomData *);

static void stop_feed(GstElement *, CustomData *);

static gboolean push_data(CustomData *);

static void error_cb(GstBus *, GstMessage *, CustomData *);

static GstFlowReturn new_sample(GstElement *, CustomData *);

void play_07(int argc, char *argv[])
{
    GstPad *tee_audio_pad;
    GstPad *queue_audio_pad;

    GstPad *tee_video_pad;
    GstPad *queue_video_pad;

    GstPad *tee_app_pad;
    GstPad *queue_app_pad;

    CustomData data;
    GstBus *bus;
    GstMessage *message;

    GstAudioInfo info;
    GstCaps *audio_caps;

    gst_init(&argc, &argv);
    memset(&data, 0, sizeof(data));
    data.b = 1;
    data.d = 1;

    data.app_src = gst_element_factory_make("appsrc", "app_src");
    data.tee = gst_element_factory_make("tee", "tee");

    data.audio_queue = gst_element_factory_make("queue", "audio_queue");
    data.audio_convert = gst_element_factory_make("audioconvert", "audio_convert");
    data.audio_resample = gst_element_factory_make("audioresample", "audio_resample");
    data.audio_sink = gst_element_factory_make("autoaudiosink", "audio_sink");

    data.video_queue = gst_element_factory_make("queue", "video_queue");
    data.audio_convert1 = gst_element_factory_make("audioconvert", "aduio_convert1");
    data.visual = gst_element_factory_make("wavescope", "visual");
    data.video_convert = gst_element_factory_make("videoconvert", "csp");
    data.video_sink = gst_element_factory_make("autovideosink", "video_sink");

    data.app_queue = gst_element_factory_make("queue", "app_queue");
    data.app_sink = gst_element_factory_make("appsink", "app_sink");

    data.pipeline = gst_pipeline_new("test-pipeline");

    if (!data.pipeline || !data.app_src || !data.tee || !data.audio_queue || !data.audio_convert || !data.audio_resample || !data.audio_sink || !data.video_queue || !data.audio_convert1 || !data.visual || !data.video_convert || !data.video_sink)
    {
        g_printerr("Not all elements could be created.\n");
        return;
    }

    /*configure wavescope*/
    g_object_set(data.visual, "shader", 0, "style", 1, nullptr);

    /*configure appsrc*/
    gst_audio_info_set_format(&info, GST_AUDIO_FORMAT_S16, SAMPLE_RATE, 1, nullptr);
    audio_caps = gst_audio_info_to_caps(&info);
    g_object_set(data.app_src, "caps", audio_caps, "format", GST_FORMAT_TIME, nullptr);
    g_signal_connect(data.app_src, "need-data", (GCallback)start_feed, &data);
    g_signal_connect(data.app_src, "enough-data", (GCallback)stop_feed, &data);

    /*Configure appsink*/
    g_object_set(data.app_sink, "emit-signals", true, "caps", audio_caps, nullptr);
    g_signal_connect(data.app_sink, "new_sample", G_CALLBACK(new_sample), &data);
    gst_caps_unref(audio_caps);

    gst_bin_add_many(GST_BIN(data.pipeline), data.app_src, data.tee, data.audio_queue, data.audio_convert, data.audio_resample, data.audio_sink, data.video_queue, data.audio_convert1, data.visual, data.video_convert, data.video_sink, data.app_queue, data.app_sink, nullptr);
    if (gst_element_link_many(data.app_src, data.tee, nullptr) != true ||
        gst_element_link_many(data.audio_queue, data.audio_convert, data.audio_resample, data.audio_sink, nullptr) != true ||
        gst_element_link_many(data.video_queue, data.audio_convert1, data.visual, data.video_convert, data.video_sink, nullptr) != true ||
        gst_element_link_many(data.app_queue, data.app_sink, nullptr) != true)
    {
        g_printerr("Elements could not be linked.\n");
        return;
    }

    tee_audio_pad = gst_element_request_pad_simple(data.tee, "src_%u");
    g_print("Obtained request pad %s for audio branch.\n", gst_pad_get_name(tee_audio_pad));
    queue_audio_pad = gst_element_get_static_pad(data.audio_queue, "sink");

    tee_video_pad = gst_element_request_pad_simple(data.tee, "src_%u");
    g_print("Obtained request pad %s for video branch.\n", gst_pad_get_name(tee_video_pad));
    queue_video_pad = gst_element_get_static_pad(data.video_queue, "sink");

    tee_app_pad = gst_element_request_pad_simple(data.tee, "src_%u");
    g_print("Obtained request pad %s for app branch.\n", gst_pad_get_name(tee_app_pad));
    queue_app_pad = gst_element_get_static_pad(data.app_queue, "sink");

    if (gst_pad_link(tee_audio_pad, queue_audio_pad) != GST_PAD_LINK_OK ||
        gst_pad_link(tee_video_pad, queue_video_pad) != GST_PAD_LINK_OK ||
        gst_pad_link(tee_app_pad, queue_app_pad) != GST_PAD_LINK_OK)
    {
        g_printerr("tee could be linked.\n");
        g_object_unref(data.pipeline);
        return;
    }
    g_object_unref(queue_audio_pad);
    g_object_unref(queue_video_pad);
    g_object_unref(queue_app_pad);


    bus = gst_element_get_bus(data.pipeline);
    gst_bus_add_signal_watch(bus);
    g_signal_connect(G_OBJECT(bus), "message::error", (GCallback)error_cb, &data);
    gst_object_unref(bus);

    gst_element_set_state(data.pipeline, GST_STATE_PLAYING);

    data.main_loop = g_main_loop_new(nullptr, false);
    g_main_loop_run(data.main_loop);

    gst_element_release_request_pad(data.tee, tee_audio_pad);
    gst_element_release_request_pad(data.tee, tee_video_pad);
    gst_element_release_request_pad(data.tee, tee_app_pad);
    gst_object_unref(tee_audio_pad);
    gst_object_unref(tee_video_pad);
    gst_object_unref(tee_app_pad);

    gst_element_set_state(data.pipeline, GST_STATE_NULL);
    g_object_unref(data.pipeline);
}

static void start_feed(GstElement * source, guint size, CustomData * data) {
    if(data->sourceid == 0) {
        g_print("start feeding.\n");
        data->sourceid = g_idle_add((GSourceFunc) push_data, data);
    }
}

static void stop_feed(GstElement * source, CustomData * data)
{
    if(data->sourceid != 0) {
        g_print("stop feeding.\n");
        g_source_remove(data->sourceid);
        data->sourceid = 0;
    }
}

static gboolean push_data(CustomData * data) {
    GstBuffer * buffer;
    GstFlowReturn ret;
    int i;
    GstMapInfo map;
    gint16 *raw;
    gint num_samples = CHUNK_SIZE / 2;
    gfloat freq;

    buffer = gst_buffer_new_and_alloc(CHUNK_SIZE);

    GST_BUFFER_TIMESTAMP(buffer) = gst_util_uint64_scale(data->num_samples, GST_SECOND, SAMPLE_RATE);
    GST_BUFFER_DURATION(buffer) = gst_util_uint64_scale(num_samples, GST_SECOND, SAMPLE_RATE);

    gst_buffer_map(buffer, &map, GST_MAP_WRITE);
    raw = (gint16 *) map.data;
    data->c += data->d;
    data->d -= data->c / 1000;
    freq = 1100 + 1000 * data->d;
    for(i = 0; i < num_samples;i++) {
        data->a += data->b;
        data->b -= data->a / freq;
        raw[i] = (gint16)(500 * data->a);
    }
    gst_buffer_unmap(buffer, &map);
    data->num_samples += num_samples;

    g_signal_emit_by_name(data->app_src, "push-buffer", buffer, &ret);

    gst_buffer_unref(buffer);

    if(ret != GST_FLOW_OK) {
        return false;
    }

    return true;
}

static void error_cb(GstBus * bus, GstMessage * message, CustomData * data) {
    GError* err;
    gchar * debug_info;

    gst_message_parse_error(message, &err, &debug_info);
    g_printerr("Error received from element %s: %s\n", GST_OBJECT_NAME(message->src), err->message);
    g_printerr("Debugging infomation: %s\n", debug_info);
    g_clear_error(&err);
    g_free(debug_info);

    g_main_loop_quit(data->main_loop);
}

static GstFlowReturn new_sample(GstElement * sink, CustomData * data) {
    GstSample * sample;

    g_signal_emit_by_name(sink, "pull-sample", &sample);
    if(sample) {
        g_print("*");
        gst_sample_unref(sample);
        return GST_FLOW_OK;
    }
    return GST_FLOW_ERROR;
}
